#ifndef CORE_TEXTURE_H
#define CORE_TEXTURE_H
#include "Types.h"
#include "Perlin.h"
#include "utils.h"
namespace RayTracer
{
	class Texture
	{
	public:
		virtual vec3 value(float u, float v, const vec3& p) const = 0;
	};



	class SolidTexture : public Texture 
	{
	public:
		SolidTexture() {}
		SolidTexture(vec3 c) : color(c) {}

		SolidTexture(float red, float green, float blue)
			: SolidTexture(vec3(red, green, blue)) {}

		virtual vec3 value(float u, float v, const vec3& p) const override 
		{
			return color;
		}
	public:
		vec3& Color() { return color; }
	private:
		vec3 color;
	};

	class CheckerTexture : public Texture {
	public:
		CheckerTexture() {}

		CheckerTexture(std::shared_ptr<Texture> _even, std::shared_ptr<Texture> _odd)
			: even(_even), odd(_odd) {}

		CheckerTexture(vec3 c1, vec3 c2)
			: even(std::make_shared<SolidTexture>(c1)), odd(std::make_shared<SolidTexture>(c2)) {}

		virtual vec3 value(float u, float v, const vec3& p) const override 
		{
			auto sines = sin(10 * p.x)*sin(10 * p.y)*sin(10 * p.z);
			if (sines < 0)
				return odd->value(u, v, p);
			else
				return even->value(u, v, p);
		}

	public:
		std::shared_ptr<Texture> odd;
		std::shared_ptr<Texture> even;
	};

	class NoiseTexture : public Texture {
	public:
		NoiseTexture() {}
		NoiseTexture(float sc) : scale(sc) {}
		virtual vec3 value(float u, float v, const vec3& p) const override 
		{
			return vec3(1, 1, 1) * 0.5f * (1 + sin(scale*p.z + 10 * noise.turb(p)));
		}

	public:
		Perlin noise;
		float scale;
	};


	class ImageTexture : public Texture {
	public:
		const static int bytes_per_pixel = 3;

		ImageTexture()
			: data(nullptr), width(0), height(0), bytes_per_scanline(0) {}

		ImageTexture(const char* filename) {
			int components_per_pixel = bytes_per_pixel;

			data = Utils::LoadPixel(filename, width, height, components_per_pixel,false);

			if (!data) {
				std::cerr << "ERROR: Could not load texture image file '" << filename << "'.\n";
				width = height = 0;
			}

			bytes_per_scanline = bytes_per_pixel * width;
		}

		~ImageTexture() {
			delete data;
		}

		virtual vec3 value(float u, float v, const vec3& p) const override {
			// If we have no texture data, then return solid cyan as a debugging aid.
			if (data == nullptr)
				return vec3(0, 1, 1);

			// Clamp input texture coordinates to [0,1] x [1,0]
			u = clamp(u, 0.0, 1.0);
			v = 1.0 - clamp(v, 0.0, 1.0);  // Flip V to image coordinates

			auto i = static_cast<int>(u * width);
			auto j = static_cast<int>(v * height);

			// Clamp integer mapping, since actual coordinates should be less than 1.0
			if (i >= width)  i = width - 1;
			if (j >= height) j = height - 1;

			const auto color_scale = 1.0 / 255.0;
			auto pixel = data + j * bytes_per_scanline + i * bytes_per_pixel;

			return vec3(color_scale*pixel[0], color_scale*pixel[1], color_scale*pixel[2]);
		}

	private:
		unsigned char *data;
		int width, height;
		int bytes_per_scanline;
	};
}
#endif